﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using GT.Net;
using System.Timers;
using System.Net;
using System.Globalization;

namespace Tracer
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {

        #region Window/Drawing Code

        Color pointerColor;

        /// <summary>
        /// Performs custom drawing in the window.
        /// </summary>
        private class Drawer : FrameworkElement
        {
            /// <summary>
            /// Draws tails to the pointers.
            /// </summary>
            /// <param name="drawingContext"></param>
            protected override void OnRender(DrawingContext drawingContext)
            {
                ICollection<CircleDrawer> remainingCircleDrawers = new List<CircleDrawer>();
                lock (circleDrawers)
                {
                    foreach (CircleDrawer drawer in circleDrawers)
                    {
                        drawer.Draw(drawingContext);
                        if (!drawer.IsDead)
                            remainingCircleDrawers.Add(drawer);
                    }
                    circleDrawers = remainingCircleDrawers;
                }
            }
        }

        public Window1()
        {
            InitializeComponent();
            OnMouseDown(null);
            //rdbRed.IsChecked = true;

            //Put our IP address in the title bar.
            String hostname;
            hostname = Dns.GetHostName();
            IPHostEntry ipEntry = Dns.GetHostEntry(hostname);
            IPAddress[] addresses = ipEntry.AddressList;
            String windowTitle = "IP ADDRESSES: ";
            for (int i = 0; i < addresses.Length; i++)
            {
                windowTitle = windowTitle + addresses[i].ToString() + ", ";
            }
            this.Title = windowTitle;

            //Add the drawing object to the window.
            this.cnvDrawingSurface.Children.Add(drawer);

        }

        #region Drawing Fields

        private static ICollection<CircleDrawer> circleDrawers = new List<CircleDrawer>();
        private Drawer drawer = new Drawer();
        private delegate void VoidDelegate();

        #endregion

        /// <summary>
        /// Processes the position update message.
        /// </summary>
        private void Process(PositionUpdateMessage message)
        {
            lock (circleDrawers)
            {
                circleDrawers.Add(new CircleDrawer(message.Location, message.PointerColor));
            }
        }

        private void rdbRed_Checked(object sender, RoutedEventArgs e)
        {
            pointerColor = Colors.Red;
        }

        private void rdbBlue_Checked(object sender, RoutedEventArgs e)
        {
            pointerColor = Colors.Blue;
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            Random r = new Random(DateTime.Now.Millisecond);
            pointerColor = new Color();
            byte[] b = new byte[3];
            r.NextBytes(b);
            pointerColor.B = b[0];
            pointerColor.G = b[1];
            pointerColor.R = b[2];
        }

        #endregion

        #region GT Code


        #region Relevant Fields

        IObjectChannel objectChannel;
        Client client;
        Timer updateTimer;
        const double UPDATE_INTERVAL_ms = 1000 / 30.0;
        const string PORT_NUMBER = "9999";
        const byte CHANNEL_ID = (Byte)0;
        bool isConnected = false;

        #endregion

        /// <summary>
        /// Connects to the address displayed in txtServerAddress
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConnect_Click(object sender, RoutedEventArgs e)
        {
            /*If we're already connected, just exit.*/
            if (isConnected)
                return;

            /* Create a client for this instance of the program.
             * Notes:
             * -    The client has several events which you can subscribe to. The ErrorEvent is one (very useful) example.
             */ 
            client = new Client();

            /* Open an object channel to the server.
             * Notes:
             * -    Different channel types can be opened, such as string channels and binary channels.
             * -    The ChannelDeliveryRequirements parameter change how messages are sent over the channel. 
             *      You can use pre-built ones (such as ChannelDeliveryRequirements.CommandsLike, which enforces 
             *      message ordering, guaranteed delivery, and prevents message aggregation), or create your own custom
             *      requirements instance. 
             * -    client.OpenObjectChannel will throw an exception if it cannot connect to the given address.
             * -    CHANNEL_ID is 0 in this example (and is of type "byte").
             * -    Remember to start the client repeater before you try and connect!
             * -    Firewalls can hinder connectivity, but often are not a problem.
             * -    The university has a *terrible* network setup in Thorvaldson. If you can't connect to computers A, B, and C
             *      (even after disabling firewalls) but you *can* connect to your own computer and computers D, E, and F, 
             *      then its almost certainly the infrastructure's fault, not yours.
             */
            try
            {
                client.Start();
                objectChannel = client.OpenObjectChannel(txtServerAddress.Text, PORT_NUMBER, CHANNEL_ID, ChannelDeliveryRequirements.CommandsLike);
                isConnected = true;
                btnConnect.IsEnabled = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Could not connect to given address. \nProblem:\n\t" + ex.Message);
            }

            /* Start an update timer.
             * Notes:
             * -    Timer is required to update the client and retrieve messages.
             */
            updateTimer = new Timer(UPDATE_INTERVAL_ms);
            updateTimer.Elapsed += new ElapsedEventHandler(updateTimer_Elapsed);
            updateTimer.Start();

        }

        /// <summary>
        /// Updates the client and receives messages.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            /*If we're not connected to any repeater, then simply exit.*/
            if (!isConnected)
                return;

            //Update the client, receiving messages.
            client.Update();

            /* While messages exist on the message queue, process them.
             * Notes:
             * -    If you want to handle all mesasges in order, use OLDEST_MESSAGE = 0.
             * -    If you want to handle newest messages first, use OLDEST_MESSAGE = (queue's size-1).
             */
            const int OLDEST_MESSAGE = 0;
            object message;
            while ((message = objectChannel.DequeueMessage(OLDEST_MESSAGE)) != null)
            {
                /* Determine the received message and process it.
                 * How you process the message is up to you. Although I don't use it here, the Command pattern is very handy.
                 */
                if (message is PositionUpdateMessage)
                {
                    Process((PositionUpdateMessage)message);
                }
            }

            /*Refresh the window for drawing purposes. You can do this wherever it is appropriate.*/
            Delegate d = new VoidDelegate(drawer.InvalidateVisual);
            Dispatcher.BeginInvoke(d, System.Windows.Threading.DispatcherPriority.Render, null);
        }

        /// <summary>
        /// Sends a position update message when the mouse moves.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            /*If we're not connected, don't send any messages.*/
            if (!isConnected)
                return;

            /* Send the position and color of our telepointer. 
             * Notes:
             * -    Sending messages is very easy in GT. 
             *      -   Decide which channel will send the message
             *      -   use <Channel>.Send(...)
             *      -   The object to send must be marked as Serializable (place [Serializable] above the class declaration).
             *      
             *      * CAUTION! *
             *      WHEN USING THE OBJECT CHANNEL:
             *          -   GT (by default) serializes instances before sending them across the object channel.
             *          -   Therefore, when GT sends an instance over the object channel, 
             *              it also sends any referenced instance along with it.
             *          -   Ie: Class A contains a reference to an instance of Class B. 
             *              When Class A is sent, the referenced instance is also sent!
             *          -   Avoid sending objects with many pointers. You may unintentionally send massive amounts of data! 
             *          -   Unique identifiers are very useful if you need to replace a pointer.
             *         
             * -    GT has maximum message sizes. Should you need to send larger messages, change
             *      UdpClientTransport.DefaultMaximumMessageSize and TcpTransport.DefaultMaximumMessageSize
             *      to appropriate values.
             *      
             */
            objectChannel.Send(new PositionUpdateMessage(pointerColor, e.GetPosition(this)));
        }

        #endregion
    }
}
